/*
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下，排序 n 个项目要 Ο(nlogn) 次比较。
在最坏状况下则需要 Ο(n2) 次比较，但这种状况并不常见。
事实上，快速排序通常明显比其他 Ο(nlogn) 算法更快，因为它的内部循环（inner loop）可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法（Divide and conquer）策略来把一个串行（list）分为两个子串行（sub-lists）。

快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看，快速排序应该算是在冒泡排序基础上的递归分治法。

快速排序的名字起的是简单粗暴，因为一听到这个名字你就知道它存在的意义，就是快，而且效率高！
它是处理大数据最快的排序算法之一了。
虽然 Worst Case 的时间复杂度达到了 O(n²)，但是人家就是优秀，在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好，
可是这是为什么呢，我也不知道。好在我的强迫症又犯了，查了 N 多资料终于在《算法艺术与信息学竞赛》上找到了满意的答案：

    快速排序的最坏运行情况是 O(n²)，比如说顺序数列的快排。
    但它的平摊期望时间是 O(nlogn)，且 O(nlogn) 记号中隐含的常数因子很小，比复杂度稳定等于 O(nlogn) 的归并排序要小很多。
    所以，对绝大多数顺序性较弱的随机数列而言，快速排序总是优于归并排序。

1. 算法步骤
    a.从数列中挑出一个元素，称为 "基准"（pivot）;
    b.重新排序数列，所有元素比基准值小的摆放在基准前面，所有元素比基准值大的摆在基准的后面（相同的数可以到任一边）。在这个分区退出之后，该基准就处于数列的中间位置。这个称为分区（_partition）操作；
    c.递归地（recursive）把小于基准值元素的子数列和大于基准值元素的子数列排序；
 */

/*
2.快速排序:

 解析：
    快速排序是对冒泡排序的一种改进，
    第一趟排序时将数据分成两部分，一部分比另一部分的所有数据都要小。
    然后递归调用，在两边都实行快速排序。
 */
function quickSort(arr) {
    // arr长度小于等于1直接return 这个判断是方法的核心(debug走一下就知道了)
    if (arr.length <= 1) {
        return arr
    }

    // 获取arr中间这个元素的下标
    let pivotIndex = Math.floor(arr.length / 2)
    // 从arr数组剪切(弹出/取出)这个中间元素 (注意!需要从arr中删除这个中间元素)
    let pivot = arr.splice(pivotIndex, 1)[0] // splice(index,howmany,item1,.....,itemX) 剪切，删除或添加；如果从 arr 中删除了元素，则返回的是含有被删除的元素的数组
    // 保存arr左边的元素
    let left = []
    // 保存arr右边的元素
    let right = []

    // 遍历arr，以pivotEle为标杆 将arr分成左右两部分
    for (let i = 0; i < arr.length; i++) {
        // 如果小于中间的元素，push到leftArr，否则push到rightEle
        if (arr[i] < pivot) {
            left.push(arr[i]) // push() 方法可向数组的末尾添加一个或多个元素，并返回新的长度
        } else {
            right.push(arr[i])
        }
    }

    // 递归调用
    return quickSort(left).concat([pivot], quickSort(right)) // concat(array2,array3,...,arrayX) 连接两个或更多的数组，并返回结果。
}

/**
 * 分区操作
 * @param arr
 * @param low
 * @param high
 */
function _partition(arr, low, high) {
    // 选取基准值
    const pivot = arr[low]
    while (low < high) {
        while (low < high && arr[high] > pivot) {
            --high
        }
        arr[low] = arr[high]

        while (low < high && arr[low] <= pivot) {
            ++low
        }
        arr[high] = arr[low]
    }
    arr[low] = pivot

    // 返回新的基准值下标
    return low
}

function quickSort2(arr, low, high) {
    low = typeof low === 'number' ? low : 0
    high = typeof high === 'number' ? high : arr.length - 1

    if (low <= high) {
        const partitionIndex = _partition(arr, low, high)
        quickSort2(arr, low, partitionIndex - 1)
        quickSort2(arr, partitionIndex + 1, high)
    }

    return arr
}

console.log(quickSort([9, 1, 5, 4, 12, 33, 0]))
console.log(quickSort([9, 1, 5, 4, 12, 33, 0, 5]))
console.log(quickSort([9, 1, 5, 4, 12, 33, 0, 12.2, 2.5, 5.3, 4]))

// [ 0, 1, 4, 5, 9, 12, 33 ]
// [ 0, 1, 4, 5, 5, 9, 12, 33 ]
// [ 0, 1, 2.5, 4, 4, 5, 5.3, 9, 12, 12.2, 33 ]

console.log(quickSort2([9, 1, 5, 4, 12, 33, 0]))
console.log(quickSort2([9, 1, 5, 4, 12, 33, 0, 5]))
console.log(quickSort2([9, 1, 5, 4, 12, 33, 0, 12.2, 2.5, 5.3, 4]))

// [ 0, 1, 4, 5, 9, 12, 33 ]
// [ 0, 1, 4, 5, 5, 9, 12, 33 ]
// [ 0, 1, 2.5, 4, 4, 5, 5.3, 9, 12, 12.2, 33 ]
